﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using BlockeyEssentials;
using BlockeyEssentials.Input;
using Microsoft.Xna.Framework.Input;

namespace Blockey.GameObjects3D
{
    public abstract class Moveable3D
    {
        public Moveable3D()
        {
            _Position = Vector3.Zero;
            _PositionShift = Vector3.Zero;
            _Velocity = Vector3.Zero;
            _Direction = Vector3.Zero;
            _LastDirection = Vector3.UnitX;
            _Magnitude = MAGNITUDE_DEFAULT;
            _Friction = FRICTION_DEFAULT;
            _Radius = RADIUS_DEFAULT;
            _GravityPower = GRAVITYPOWER_DEFAULT;
            _ReactToCollision = true;

            _PrevPosition = Vector3.Zero;
            _PrevVelocity = Vector3.Zero;
            _PrevDirection = Vector3.Zero;
            _OriginalPosition = Vector3.Zero;
            _Spheres = new List<BoundingSphere>();
        }

        #region Methods

        public virtual void Move()
        {
            if (Networking.IsRunning && Networking.Session.IsHost == false)
            {
                var oShift = (PositionDestination - PrevPosition) * (1f / (float)Networking.FrameSkip);
                Position += oShift;
            }
            else
            {
                _PrevDirection = _Direction;
                if (_Direction.Length() != 0)
                {
                    _LastDirection = _Direction;
                }
                _PrevVelocity = _Velocity;
                _PrevPosition = _Position;

                var oResistance = -_Velocity * _Friction;
                Vector3 oAcceleration = Vector3.Zero;
                if (_Position.Z == 0)
                {
                    oAcceleration = (_Direction * _Magnitude) + oResistance;
                }
                else
                {
                    oResistance.Z = 0;
                    oAcceleration = (GRAVITY * _GravityPower) + oResistance;
                }

                Velocity += oAcceleration;
                Position += _Velocity;

                if (_Position.Z <= 0)
                {
                    _Position.Z = 0;

                    if (VelocityZ < 2)
                        VelocityZ = 0;
                    else
                        _Velocity.Z = Math.Abs(VelocityZ);
                }

            }

            UpdateBoundingSpheres();
        }

        protected virtual void UpdateBoundingSpheres()
        {
            _Spheres.Clear();
            _Spheres.Add(new BoundingSphere(_Position, _Radius));
        }

        public static Moveable3D FindClosest(Moveable3D oMoveable, List<Moveable3D> oCheckers)
        {
            float? oDistance = null;
            Moveable3D oClosest = null;
            foreach (var oCheck in oCheckers)
            {
                var oDistanceCurrent = Vector3.Distance(oCheck.Position, oMoveable.Position);
                if (oDistance == null || oDistanceCurrent < oDistance)
                {
                    oDistance = oDistanceCurrent;
                    oClosest = oCheck;
                }
            }

            return oClosest;
        }

        public virtual void UpdateRotation(bool bIsClient, bool bRotateImmediately)
        {
            if (bIsClient == false)
            {
                _Rotation.Z = All.VectorToAngle(new Vector2(_LastDirection.X, _LastDirection.Y)) - MathHelper.PiOver2;
            }

            if (_Rotation.Z > _RotationCurrent.Z + MathHelper.Pi)
                _Rotation.Z = MathHelper.WrapAngle(_Rotation.Z);
            else if (_Rotation.Z < _RotationCurrent.Z - MathHelper.Pi)
                _Rotation.Z = MathHelper.WrapAngle(_Rotation.Z);

            var oDelta = _Rotation.Z - _RotationCurrent.Z;
            if (oDelta > MathHelper.Pi)
                _RotationCurrent.Z += MathHelper.TwoPi;
            if (oDelta < -MathHelper.Pi)
                _RotationCurrent.Z -= MathHelper.TwoPi;

            if (bRotateImmediately)
            {
                _RotationCurrent = _Rotation;
            }
            else
            {
                _RotationCurrent = Vector3.Lerp(_RotationCurrent, _Rotation, 0.1f);
            }
        }

        public void CollisionReactionWith(Rink oRink)
        {
            if (PositionX < oRink.BoundingSphereLD.Center.X &&
                PositionY < oRink.BoundingSphereLD.Center.Y)
            {
                foreach (BoundingSphere oSphere in _Spheres)
                {
                    if (oRink.BoundingSphereLD.Contains(oSphere) != ContainmentType.Contains)
                    {
                        Vector3 oDirection = Position - oRink.BoundingSphereLD.Center;
                        oDirection.Normalize();

                        Position = oRink.BoundingSphereLD.Center + oDirection * (oRink.BoundingSphereLD.Radius - _Radius);
                        var oVelo = Velocity;
                        Velocity = Vector3.Reflect(Velocity, -oDirection);
                    }
                }
            }
            if (PositionX < oRink.BoundingSphereLU.Center.X &&
                PositionY > oRink.BoundingSphereLU.Center.Y)
            {
                foreach (BoundingSphere oSphere in _Spheres)
                {
                    if (oRink.BoundingSphereLU.Contains(oSphere) != ContainmentType.Contains)
                    {
                        Vector3 oDirection = Position - oRink.BoundingSphereLU.Center;
                        oDirection.Normalize();

                        Position = oRink.BoundingSphereLU.Center + oDirection * (oRink.BoundingSphereLU.Radius - _Radius);
                        var oVelo = Velocity;
                        Velocity = Vector3.Reflect(Velocity, -oDirection);
                    }
                }
            }
            if (PositionX > oRink.BoundingSphereRD.Center.X &&
                PositionY < oRink.BoundingSphereRD.Center.Y)
            {
                foreach (BoundingSphere oSphere in _Spheres)
                {
                    if (oRink.BoundingSphereRD.Contains(oSphere) != ContainmentType.Contains)
                    {
                        Vector3 oDirection = Position - oRink.BoundingSphereRD.Center;
                        oDirection.Normalize();

                        Position = oRink.BoundingSphereRD.Center + oDirection * (oRink.BoundingSphereRD.Radius - _Radius);
                        var oVelo = Velocity;
                        Velocity = Vector3.Reflect(Velocity, -oDirection);
                    }
                }
            }
            if (PositionX > oRink.BoundingSphereRU.Center.X &&
                PositionY > oRink.BoundingSphereRU.Center.Y)
            {
                foreach (BoundingSphere oSphere in _Spheres)
                {
                    if (oRink.BoundingSphereRU.Contains(oSphere) != ContainmentType.Contains)
                    {
                        Vector3 oDirection = Position - oRink.BoundingSphereRU.Center;
                        oDirection.Normalize();

                        Position = oRink.BoundingSphereRU.Center + oDirection * (oRink.BoundingSphereRU.Radius - _Radius);
                        var oVelo = Velocity;
                        Velocity = Vector3.Reflect(Velocity, -oDirection);
                    }
                }
            }

            else
            {
                foreach (var oSphere in _Spheres)
                {
                    if (oRink.BoundingBox.Contains(oSphere) != ContainmentType.Contains)
                    {
                        if (PositionX + _Radius > oRink.BoundingBox.Max.X)
                        {
                            VelocityX = -Math.Abs(VelocityX);
                            PositionX = oRink.BoundingBox.Max.X - _Radius;
                        }
                        if (PositionX - _Radius < oRink.BoundingBox.Min.X)
                        {
                            VelocityX = Math.Abs(VelocityX);
                            PositionX = oRink.BoundingBox.Min.X + _Radius;
                        }
                        if (PositionY + _Radius > oRink.BoundingBox.Max.Y)
                        {
                            VelocityY = -Math.Abs(VelocityY);
                            PositionY = oRink.BoundingBox.Max.Y - _Radius;
                        }
                        if (PositionY - _Radius < oRink.BoundingBox.Min.Y)
                        {
                            VelocityY = Math.Abs(VelocityY);
                            PositionY = oRink.BoundingBox.Min.Y + _Radius;
                        }
                    }
                }
            }

        }

        public bool CollidesWith(Moveable3D oMoveable)
        {
            foreach (var oSphere in oMoveable.Spheres)
            {
                if (CollidesWith(oSphere))
                    return true;
            }
            return false;
        }

        public bool CollidesWith(BoundingBox oBox)
        {
            foreach (var oCollisionSphere in this.Spheres)
            {
                if (oCollisionSphere.Intersects(oBox))
                    return true;
            }
            return false;
        }

        public bool CollidesWith(BoundingSphere oSphere)
        {
            foreach (var oCollisionSphere in this.Spheres)
            {
                if (oCollisionSphere.Intersects(oSphere))
                    return true;
            }
            return false;
        }

        public bool CollidesWith(Ray oRay)
        {
            foreach (var oCollisionSphere in this.Spheres)
            {
                if (oRay.Intersects(oCollisionSphere) != null)
                    return true;
            }
            return false;
        }

        public abstract void DrawShadows(GraphicsDevice oGraphics, Camera3D oCamera);

        protected void SetShadow()
        {
            List<VertexPositionNormalTexture> oVPNT = new List<VertexPositionNormalTexture>();

            var oLength = 1;
            var leftforwbottom = new Vector3(-oLength, oLength, 0);
            var leftbackbottom = new Vector3(-oLength, -oLength, 0);
            var rightforwbottom = new Vector3(oLength, oLength, 0);
            var rightbackbottom = new Vector3(oLength, -oLength, 0);

            var oTopLeft = new Vector2(0, 0);
            var oTopRight = new Vector2(1, 0);
            var oBottomLeft = new Vector2(0, 1);
            var oBottomRight = new Vector2(1, 1);

            oVPNT.Add(new VertexPositionNormalTexture(leftforwbottom, Vector3.UnitZ, oTopLeft));
            oVPNT.Add(new VertexPositionNormalTexture(rightforwbottom, Vector3.UnitZ, oTopRight));
            oVPNT.Add(new VertexPositionNormalTexture(rightbackbottom, Vector3.UnitZ, oBottomRight));
            oVPNT.Add(new VertexPositionNormalTexture(leftforwbottom, Vector3.UnitZ, oTopLeft));
            oVPNT.Add(new VertexPositionNormalTexture(rightbackbottom, Vector3.UnitZ, oBottomRight));
            oVPNT.Add(new VertexPositionNormalTexture(leftbackbottom, Vector3.UnitZ, oBottomLeft));

            _Shadow.SetVertices(oVPNT);
        }

        public void SetStartPosition(Vector3 oStart)
        {
            Position = oStart;
            _OriginalPosition = oStart;
        }

        #endregion

        #region Properties

        public Vector3 Position
        {
            get { return _Position; }
            set
            {
                _Position = value;
            }
        }
        public Vector3 Velocity
        {
            get { return _Velocity; }
            set
            {
                _Velocity = value;
            }
        }
        public Vector3 Direction
        {
            get { return _Direction; }
            set { _Direction = value; }
        }
        public float Magnitude
        {
            get { return _Magnitude; }
            set { _Magnitude = value; }
        }
        public float Friction
        {
            get { return _Friction; }
            set { _Friction = value; }
        }
        public float Radius
        {
            get { return _Radius; }
            set { _Radius = value; }
        }

        public Vector3 LastDirection
        {
            get { return _LastDirection; }
            set { _LastDirection = value; }
        }

        public float PositionX
        {
            get { return _Position.X; }
            set { _Position.X = value; }
        }
        public float PositionY
        {
            get { return _Position.Y; }
            set { _Position.Y = value; }
        }
        public float PositionZ
        {
            get { return _Position.Z; }
            set { _Position.Z = value; }
        }
        public float VelocityX
        {
            get { return _Velocity.X; }
            set { _Velocity.X = value; }
        }
        public float VelocityY
        {
            get { return _Velocity.Y; }
            set { _Velocity.Y = value; }
        }
        public float VelocityZ
        {
            get { return _Velocity.Z; }
            set { _Velocity.Z = value; }
        }

        public Vector3 PrevPosition
        {
            get { return _PrevPosition; }
            set
            { _PrevPosition = value; }
        }
        public Vector3 PrevVelocity
        {
            get { return _PrevVelocity; }
            set { _PrevVelocity = value; }
        }
        public Vector3 PrevDirection
        {
            get { return _PrevDirection; }
            set { _PrevDirection = value; }
        }
        public Vector3 OriginalPosition
        {
            get { return _OriginalPosition; }
        }

        public List<BoundingSphere> Spheres
        {
            get { return _Spheres; }
        }
        public BoundingSphere BaseSphere
        {
            get { return _Spheres[0]; }
        }

        public Vector3 PositionDestination
        {
            get;
            set;
        }
        public Vector3 Rotation
        {
            get { return _Rotation; }
            set { _Rotation = value; }
        }
        public Vector3 CurrentRotation
        {
            get { return _RotationCurrent; }
            set { _RotationCurrent = value; }
        }

        public bool CanReactToCollision
        {
            get { return _ReactToCollision; }
            set { _ReactToCollision = value; }
        }

        #endregion

        protected Vector3 _Position;
        protected Vector3 _PositionShift;
        protected Vector3 _Velocity;
        protected Vector3 _Direction;
        protected Vector3 _LastDirection;
        protected Vector3 _Rotation;
        protected Vector3 _RotationCurrent;
        protected float _Magnitude;
        protected float _Friction;
        protected float _Radius;
        protected bool _ReactToCollision;
        protected float _GravityPower;

        private List<BoundingSphere> _Spheres;

        private Vector3 _PrevPosition;
        private Vector3 _PrevVelocity;
        private Vector3 _PrevDirection;
        private Vector3 _OriginalPosition;

        protected DrawableCustom _Shadow;

        public float FRICTION_DEFAULT = 0.03f;
        public float MAGNITUDE_DEFAULT = 0.0175f;
        public float RADIUS_DEFAULT = 1.0f;
        public float GRAVITYPOWER_DEFAULT = 0.05f;
        private readonly Vector3 GRAVITY = new Vector3(0, 0, -1);

    }
}
